5.06. Справочник по C++
Справочник по C++
Общие сведения
C++ — это компилируемый, статически типизированный, мультипарадигмальный язык программирования, поддерживающий процедурное, объектно-ориентированное, обобщённое и функциональное программирование. Он предоставляет низкоуровневый доступ к памяти, управление ресурсами через RAII (Resource Acquisition Is Initialization), и мощную систему шаблонов.
Язык стандартизирован комитетом ISO/IEC JTC1/SC22/WG21. Основные версии стандарта:
- C++98 — первая официальная версия
- C++03 — технические исправления
- C++11 — крупное обновление с поддержкой современных практик
- C++14 — уточнения и расширения
- C++17 — улучшения стандартной библиотеки и языковых возможностей
- C++20 — модули, концепции, корутины, диапазоны
- C++23 — текущий актуальный стандарт на 2026 год
Компиляторы: GCC, Clang, MSVC, Intel C++ Compiler.
Базовые типы данных
Целочисленные типы
| Тип | Размер (обычно) | Диапазон значений | Знаковый |
|---|---|---|---|
bool | 1 байт | false, true | беззнаковый |
char | 1 байт | -128…127 или 0…255 | зависит от реализации |
signed char | 1 байт | -128…127 | знаковый |
unsigned char | 1 байт | 0…255 | беззнаковый |
short | 2 байта | -32 768…32 767 | знаковый |
unsigned short | 2 байта | 0…65 535 | беззнаковый |
int | 4 байта | -2 147 483 648…2 147 483 647 | знаковый |
unsigned int | 4 байта | 0…4 294 967 295 | беззнаковый |
long | 4 или 8 байт | зависит от платформы | знаковый |
unsigned long | 4 или 8 байт | зависит от платформы | беззнаковый |
long long | 8 байт | -9 223 372 036 854 775 808…9 223 372 036 854 775 807 | знаковый |
unsigned long long | 8 байт | 0…18 446 744 073 709 551 615 | беззнаковый |
Типы с фиксированным размером (из <cstdint>):
int8_t,int16_t,int32_t,int64_tuint8_t,uint16_t,uint32_t,uint64_tintptr_t,uintptr_t— для хранения указателей как целых чисел
Вещественные типы
| Тип | Размер | Пример точности | Пример диапазона |
|---|---|---|---|
float | 4 байта | ~7 десятичных цифр | ±1.2×10⁻³⁸ … ±3.4×10³⁸ |
double | 8 байт | ~15–17 цифр | ±2.2×10⁻³⁰⁸ … ±1.8×10³⁰⁸ |
long double | 10–16 байт | зависит от реализации | расширенный диапазон |
Специальные значения для вещественных типов
INFINITY— положительная бесконечность-INFINITY— отрицательная бесконечностьNaN— «Not a Number», результат недопустимых операций (например,0.0 / 0.0)- Доступ через
<cmath>или<limits>
Типы указателей и ссылок
- Указатель:
T*— хранит адрес объекта типаT - Ссылка:
T&— псевдоним существующего объекта - Rvalue-ссылка:
T&&— используется для перемещения и perfect forwarding
Пустой тип
void— отсутствие типа; используется в сигнатурах функций, не возвращающих значение, или в указателях (void*)
Типы из стандартной библиотеки
std::size_t— беззнаковый тип для размеров и индексовstd::ptrdiff_t— знаковый тип для разности указателейstd::nullptr_t— тип литералаnullptr
Литералы
Целочисленные литералы
- Десятичные:
42,0 - Восьмеричные:
042(начинаются с0) - Шестнадцатеричные:
0x2A,0XFF - Двоичные (C++14):
0b101010
Суффиксы:
uилиU— беззнаковый (42u)lилиL—long(42L)llилиLL—long long(42LL)- Комбинации:
42ULL,123uLL
Вещественные литералы
- Десятичные:
3.14,.5,1. - Экспоненциальная форма:
1.23e-4,6.022e23f - Суффиксы:
fилиF—float(3.14f)lилиL—long double(3.14L)- Без суффикса —
double
Символьные и строковые литералы
- Символ:
'a','\n','\x41','\u0041','\U00000041' - Многосимвольный литерал:
'ABCD'(реализация-зависимыйint) - Строки:
"Hello"—const char[N]u8"Привет"— UTF-8 строка (const char[N])u"юникод"— UTF-16 (const char16_t[N])U"Unicode"— UTF-32 (const char32_t[N])L"wide"— широкая строка (const wchar_t[N])
C++11 и выше:
- Сырые строки:
R"(C:\Path\To\File)"— символы\не экранируются - Пользовательские литералы:
42_km,"hello"_s(определяются черезoperator"")
Логические литералы
true,false— значения типаbool
Указательный литерал
nullptr— нулевой указатель (типstd::nullptr_t)
Операторы
Арифметические
+— унарный плюс, сложение-— унарный минус, вычитание*— умножение/— деление%— остаток от деления (только для целых типов)
Побитовые
~— побитовое НЕ&— побитовое И|— побитовое ИЛИ^— побитовое исключающее ИЛИ<<— сдвиг влево>>— сдвиг вправо (арифметический для знаковых, логический для беззнаковых)
Логические
!— логическое НЕ&&— логическое И (с коротким замыканием)||— логическое ИЛИ (с коротким замыканием)
Операторы сравнения
==— равно!=— не равно<— меньше<=— меньше или равно>— больше>=— больше или равно<=>— оператор трехстороннего сравнения (C++20, "spaceship operator")
Операторы присваивания
=— простое присваивание+=,-=,*=,/=,%=&=,|=,^=<<=,>>=
Инкремент и декремент
++x— префиксный инкремент (возвращает новое значение)x++— постфиксный инкремент (возвращает старое значение)--x,x--— аналогично для декремента
Условный оператор
condition ? expr1 : expr2— тернарный условный оператор
Операторы работы с памятью
*— разыменование указателя&— взятие адресаnew— динамическое выделение памятиdelete— освобождение памяти (дляnew)new[],delete[]— для массивов
Операторы доступа
.— доступ к члену объекта->— доступ к члену через указатель::— разрешение области видимости (scope resolution)[]— индексация массива или контейнера
Прочие операторы
,— оператор запятая (выполняет левое выражение, возвращает правое)sizeof— размер в байтах (sizeof(int),sizeof x)sizeof...— размер параметров шаблона-пакета (C++11)alignof— выравнивание типаnoexcept— проверка, может ли выражение выбросить исключениеtypeid— информация о типе во время выполнения (RTTI)dynamic_cast,static_cast,reinterpret_cast,const_cast— явные приведения типов
Управляющие конструкции
Условные операторы
if / else if / else
if (условие) {
// выполняется, если условие истинно
} else if (другое_условие) {
// выполняется, если первое ложно, а второе истинно
} else {
// выполняется, если все условия ложны
}
C++17 позволяет объявлять переменную прямо в условии:
if (int x = getValue(); x > 0) {
// x доступен только внутри этого блока
}
switch
Работает с целочисленными типами, перечислениями и char.
switch (выражение) {
case значение1:
// код
break;
case значение2:
// код
break;
default:
// выполняется, если ни один case не совпал
}
Особенности:
- Поддерживает «проваливание» (
fallthrough) — выполнение несколькихcaseподряд безbreak - C++17: атрибут
[[fallthrough]]явно указывает намеренное проваливание - C++14:
switchможет использовать инициализацию в условии (аналогичноif)
Циклы
while
while (условие) {
// тело цикла
}
Проверка условия — перед каждой итерацией.
do-while
do {
// тело цикла
} while (условие);
Тело выполняется хотя бы один раз.
for (традиционный)
for (инициализация; условие; инкремент) {
// тело
}
Пример:
for (int i = 0; i < 10; ++i) { /* ... */ }
for (диапазонный, range-based, C++11)
for (элемент : контейнер) {
// обработка элемента
}
Можно использовать ссылки для модификации:
for (auto& x : vec) x *= 2;
Поддерживается любым объектом, имеющим begin() и end() (включая массивы).
Управление циклом
break— немедленный выход из циклаcontinue— переход к следующей итерации
Функции
Объявление и определение
возвращаемый_тип имя(параметры) {
// тело
}
Пример:
int add(int a, int b) {
return a + b;
}
Параметры
- По значению: копия аргумента (
int x) - По ссылке: псевдоним (
int& x) - По константной ссылке: безопасное чтение без копирования (
const int& x) - По указателю: явная передача адреса (
int* x)
Возврат значения
return выражение;— возвращает результат- Для
void-функцийreturn;завершает выполнение
Перегрузка функций
Функции с одинаковым именем, но разными параметрами:
void print(int x);
void print(double x);
void print(const std::string& s);
Компилятор выбирает подходящую версию по типам аргументов.
Аргументы по умолчанию
Указываются в объявлении:
void greet(std::string name, std::string greeting = "Hello");
Вызов: greet("Alice") → использует "Hello" как второй аргумент.
Правило: аргументы по умолчанию должны идти справа налево — нельзя пропустить средний аргумент.
Встроенные функции (inline)
Подсказка компилятору заменить вызов телом функции:
inline int square(int x) { return x * x; }
Современные компиляторы игнорируют inline как оптимизационную подсказку, но он обязателен для определения функций в заголовочных файлах без нарушения ODR (One Definition Rule).
Лямбда-выражения (C++11)
Анонимные функции, определяемые на месте:
auto f = [](int x, int y) { return x + y; };
int result = f(3, 4); // 7
Захват переменных:
[=]— захват всех внешних переменных по значению[&]— захват по ссылке[x, &y]— явный захват:xпо значению,yпо ссылке[this]— захват указателя на текущий объект
Лямбды могут быть:
mutable— позволяют изменять копии захваченных значений- иметь спецификаторы (
constexpr,noexcept) - преобразовываться в указатель на функцию (если не захватывают ничего)
Пример:
int offset = 10;
auto add_offset = [=](int x) { return x + offset; };
Классы и структуры
Объявление
class Имя {
// по умолчанию private
};
struct Имя {
// по умолчанию public
};
Различие между class и struct — только в уровне доступа по умолчанию.
Члены класса
- Поля — переменные внутри класса
- Методы — функции-члены
- Статические члены — принадлежат классу, а не экземпляру
- Дружественные функции/классы — имеют доступ к приватным членам
Уровни доступа
public— доступен извнеprivate— доступен только внутри классаprotected— доступен внутри класса и его наследников
Пример класса
class Rectangle {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const {
return width * height;
}
void scale(double factor) {
width *= factor;
height *= factor;
}
};
Конструкторы, деструкторы и специальные функции-члены
Конструкторы
- Конструктор по умолчанию: без параметров или со всеми параметрами по умолчанию
- Параметризованный конструктор: принимает аргументы
- Конструктор копирования:
ClassName(const ClassName&) - Конструктор перемещения (C++11):
ClassName(ClassName&&) - Делегирующие конструкторы (C++11): один конструктор вызывает другой
Инициализация полей через список инициализаторов:
ClassName(int x) : member(x) {}
Деструктор
Вызывается при уничтожении объекта:
~ClassName() {
// освобождение ресурсов
}
Деструктор автоматически вызывается для локальных объектов при выходе из области видимости и для динамических — при delete.
Правило пяти (Rule of Five)
Если определён хотя бы один из следующих, стоит определить все:
- Деструктор
- Конструктор копирования
- Оператор присваивания копированием (
operator=(const T&)) - Конструктор перемещения
- Оператор присваивания перемещением (
operator=(T&&))
C++11 и выше: можно явно запросить поведение по умолчанию:
ClassName(const ClassName&) = default;
ClassName& operator=(const ClassName&) = default;
Или запретить:
ClassName(const ClassName&) = delete;
const-методы
Метод, помеченный const, гарантирует, что не изменит состояние объекта:
double getValue() const { return value; }
Такой метод может вызываться на const-объектах.
static-методы и поля
static-поле: одно на весь класс, объявляется в классе, определяется вне егоstatic-метод: не имеет доступа кthis, работает только со статическими данными
Операторы
Перегрузка операторов возможна как внутри класса (operator@), так и вне (operator@(T, U)).
Часто перегружаемые:
+,-,*,/==,!=,<=>[],(),->,*<<,>>(для потоков)new,delete(редко)
Пример:
Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}
Шаблоны
Шаблоны — механизм обобщённого программирования, позволяющий писать код, независимый от конкретных типов.
Шаблоны функций
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
Использование:
int x = max(3, 5); // T = int
double y = max(2.7, 1.3); // T = double
Альтернативное ключевое слово: class вместо typename — семантически эквивалентно:
template <class T> ...
Шаблоны классов
template <typename T>
class Stack {
private:
std::vector<T> data;
public:
void push(const T& item) { data.push_back(item); }
T pop() {
T top = data.back();
data.pop_back();
return top;
}
bool empty() const { return data.empty(); }
};
Использование:
Stack<int> intStack;
Stack<std::string> stringStack;
Не типовые параметры шаблонов
Параметры могут быть значениями, а не только типами:
template <int N>
class Array {
int data[N];
public:
int& operator[](int i) { return data[i]; }
};
Использование: Array<10> arr;
Параметры по умолчанию в шаблонах
template <typename T, typename Allocator = std::allocator<T>>
class MyVector { /* ... */ };
Специализация шаблонов
Полная специализация:
template <>
class Stack<bool> {
// особая реализация для bool
};
Частичная специализация (только для классов):
template <typename T>
class Stack<T*> {
// реализация для указателей любого типа
};
Автоматическое выведение аргументов шаблона (C++17)
std::pair p(1, 2.5); // p имеет тип std::pair<int, double>
std::vector v{1, 2, 3}; // v — std::vector<int>
Концепции (Concepts, C++20)
Ограничения на типы в шаблонах:
template <typename T>
concept Integral = std::is_integral_v<T>;
template <Integral T>
T add(T a, T b) {
return a + b;
}
Или с сокращённым синтаксисом:
Integral auto add(Integral auto a, Integral auto b) {
return a + b;
}
Исключения
Механизм обработки ошибок во время выполнения.
Базовый синтаксис
try {
// код, который может выбросить исключение
if (error) throw std::runtime_error("Ошибка!");
} catch (const std::exception& e) {
// обработка
std::cerr << "Исключение: " << e.what() << '\n';
} catch (...) {
// обработка любого другого исключения
}
Типы исключений
std::exception— базовый классstd::runtime_error,std::logic_error— общие категорииstd::invalid_argument,std::out_of_range,std::bad_alloc— конкретные ошибки
Спецификаторы исключений
noexcept— функция не выбрасывает исключений:void safe_function() noexcept { /* ... */ }- Вызов
throw()устарел с C++11, заменён наnoexcept.
RAII и исключения
Ресурсы (память, файлы, соединения) должны управляться через объекты с деструкторами (например, std::unique_ptr, std::ifstream), чтобы обеспечить корректную очистку даже при выбросе исключения.
Препроцессор и этапы компиляции
Этапы обработки исходного кода
- Лексический анализ и препроцессинг — обработка директив
# - Компиляция в объектный код — генерация
.oили.obj - Компоновка (линковка) — объединение объектных файлов и библиотек в исполняемый файл
Основные директивы препроцессора
#include <file>— вставка содержимого заголовочного файла#include "file"— поиск сначала в текущей директории#define NAME value— макроопределение#define MACRO(params) replacement— макрос с параметрами#undef NAME— удаление определения#if,#ifdef,#ifndef,#else,#elif,#endif— условная компиляция#pragma— зависящие от компилятора инструкции (#pragma once)#error "message"— остановка компиляции с сообщением
Рекомендации
- Избегать макросов там, где возможны константы,
constexpr,inlineили шаблоны - Использовать include guards или
#pragma onceв заголовках - Не использовать макросы для имитации функций — они не проверяют типы и нарушают правила области видимости
Пространства имён (Namespaces)
Группировка связанных объявлений для избежания коллизий имён.
Объявление
namespace Graphics {
class Window { /* ... */ };
void drawLine(int x1, int y1, int x2, int y2);
}
Использование
- Полное имя:
Graphics::Window w; - Директива
using namespace:using namespace Graphics;
Window w; // теперь без префикса - Объявление
usingдля одного имени:using Graphics::Window;
Вложенные пространства
namespace Engine {
namespace Rendering {
class Renderer { /* ... */ };
}
}
// Или (C++17):
namespace Engine::Rendering {
class Renderer { /* ... */ };
}
Анонимные пространства
Заменяют static на уровне файла:
namespace {
int helperFunction() { return 42; } // видна только в этом файле
}
Типы: перечисления, объединения, псевдонимы
Перечисления (enum)
Обычные перечисления (C++98):
enum Color { Red, Green, Blue };
- Значения неявно конвертируются в целые
- Имена попадают в окружающую область видимости
Перечисления с областью видимости (scoped enums, C++11):
enum class Status { Active, Inactive, Pending };
- Нет неявного преобразования в целые
- Имена доступны только через
Status::Active - Можно указать базовый тип:
enum class Code : uint8_t { OK = 0, ERR = 1 };
Объединения (union)
Хранят только одно значение из списка в один момент времени:
union Data {
int i;
float f;
char str[20];
};
Особенности:
- Размер равен размеру самого большого поля
- Начиная с C++11, можно определять конструкторы и методы, если union не содержит несовместимых типов (например, с нетривиальными деструкторами)
- Безопасное использование требует внешнего тега (tagged union) или
std::variant(C++17)
Псевдонимы типов
typedef (устаревший стиль):
typedef unsigned long ulong;
typedef int (*FuncPtr)(int, int);
using (современный стиль, C++11):
using ulong = unsigned long;
using FuncPtr = int(*)(int, int);
using StringMap = std::map<std::string, int>;
Преимущества using:
- Читаемость
- Поддержка шаблонных псевдонимов:
template <typename T>
using Vec = std::vector<T, MyAllocator<T>>;
Указатели и ссылки
Указатели
Указатель — переменная, хранящая адрес другого объекта в памяти.
int x = 42;
int* p = &x; // p содержит адрес x
int y = *p; // разыменование: y = 42
Особенности:
- Указатель может быть
nullptr— нулевой указатель - Указатель может быть переназначен на другой адрес
- Арифметика указателей допустима для массивов:
p + 1,p - q - Указатель на
void(void*) — универсальный указатель, не может быть разыменован без приведения типа
Типы указателей:
- Указатель на функцию:
int (*func)(int, int) - Указатель на член класса:
int MyClass::* ptr = &MyClass::value - Указатель на метод:
void (MyClass::*method)() = &MyClass::doSomething
Ссылки
Ссылка — псевдоним существующего объекта.
int x = 10;
int& r = x; // r — это то же самое, что и x
r = 20; // x теперь равно 20
Особенности:
- Ссылка должна быть инициализирована при объявлении
- Ссылка не может быть переназначена
- Ссылка не может быть нулевой
- Ссылка занимает столько же места, сколько и сам объект (на уровне реализации — обычно передаётся как указатель, но с семантикой псевдонима)
Rvalue-ссылки (C++11)
Используются для реализации семантики перемещения и perfect forwarding.
int&& rref = 42; // привязка к временному значению
void func(int&& x); // принимает rvalue
Перемещение:
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // v1 "опустошается"
Правило трёх/пяти и ссылки
Если класс управляет ресурсами (например, динамической памятью), необходимо явно определить:
- Деструктор
- Конструктор копирования
- Оператор присваивания копированием
- (C++11) Конструктор перемещения
- (C++11) Оператор присваивания перемещением
Умные указатели
Умные указатели — RAII-обёртки над сырыми указателями, автоматически управляющие временем жизни объекта.
std::unique_ptr
- Единственный владелец объекта
- Нельзя копировать, можно только перемещать
- Автоматически вызывает
deleteпри выходе из области видимости
#include <memory>
auto ptr = std::make_unique<int>(42);
// или: std::unique_ptr<int> ptr(new int(42));
Пользовательский удалитель:
auto ptr = std::unique_ptr<FILE, decltype(&fclose)>(
fopen("file.txt", "r"), &fclose
);
std::shared_ptr
- Совместное владение через счётчик ссылок
- Объект удаляется, когда последний
shared_ptrвыходит из области видимости - Накладные расходы: счётчик ссылок в куче, атомарные операции при копировании
auto ptr1 = std::make_shared<int>(100);
auto ptr2 = ptr1; // счётчик = 2
std::weak_ptr
- Наблюдатель за объектом, управляемым
shared_ptr - Не увеличивает счётчик ссылок
- Используется для разрыва циклических зависимостей
std::weak_ptr<int> wptr = ptr1;
if (auto sptr = wptr.lock()) {
// объект ещё жив, sptr — shared_ptr на него
}
Рекомендации
- Предпочитать
std::make_uniqueиstd::make_sharedпрямомуnew - Избегать сырых
new/deleteвне низкоуровневых систем - Не хранить умные указатели в контейнерах как
void* - Не использовать
shared_ptrбез необходимости — он дорожеunique_ptr
Массивы и строки
C-массивы
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // массив неявно преобразуется в указатель на первый элемент
Недостатки:
- Нет информации о размере
- Нельзя присваивать
- Небезопасны при передаче в функции
std::array (C++11)
Безопасная замена C-массива с фиксированным размером:
#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
size_t n = arr.size(); // 5
Преимущества:
- Размер известен во время компиляции
- Поддерживает итераторы
- Может быть скопирован
std::vector
Динамический массив с автоматическим управлением памятью:
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.resize(10);
Методы:
.size()— текущее количество элементов.capacity()— выделенная память.reserve(n)— предварительное выделение памяти.shrink_to_fit()— уменьшение capacity до size
Строки
C-строки: массив char с завершающим нулём ('\0')
const char* s = "Hello";
std::string: безопасная, динамическая строка
std::string s = "Hello";
s += " World";
s.size(); // 11
Методы:
.c_str()— возвращает C-строку.data()— указатель на данные (C++17: всегда завершён\0).substr(pos, len)— подстрока.find(),.replace(),.erase()— манипуляции
std::string_view (C++17): невладеющий «вид» на строку
void process(std::string_view sv); // принимает string, C-string, литерал
Работа с памятью
Выделение памяти
new/delete— для отдельных объектовnew[]/delete[]— для массивовmalloc/free— из C, не вызывают конструкторы/деструкторы
Размещение (placement new)
Конструирование объекта в уже выделенной памяти:
alignas(MyClass) char buffer[sizeof(MyClass)];
MyClass* obj = new(buffer) MyClass(); // placement new
obj->~MyClass(); // явный вызов деструктора
Используется в аллокаторах, пулах памяти, embedded-системах.
Выравнивание
alignof(T)— требуемое выравнивание типаTalignas(N)— принудительное выравнивание переменной или структуры
Пример:
struct alignas(16) Vec4 {
float x, y, z, w;
};
Аллокаторы
Стандартные контейнеры принимают аллокаторы:
std::vector<int, MyAllocator<int>> v;
По умолчанию используется std::allocator<T>.
Основы потокобезопасности
C++11 ввёл стандартную поддержку многопоточности.
Потоки (std::thread)
#include <thread>
void task() { /* ... */ }
std::thread t(task);
t.join(); // ждать завершения
Передача аргументов:
std::thread t([](int x) { std::cout << x; }, 42);
Мьютексы (std::mutex)
Защита общих данных:
std::mutex mtx;
mtx.lock();
// критическая секция
mtx.unlock();
Лучше использовать RAII-обёртки:
std::lock_guard<std::mutex> lock(mtx); // автоматическая блокировка/разблокировка
Или std::scoped_lock (C++17) — для нескольких мьютексов без deadlock.
Атомарные операции
Для простых типов:
#include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1); // потокобезопасное увеличение
Гарантирует, что операция выполняется целиком, без прерываний.
Future и promise
Асинхронный результат:
std::future<int> fut = std::async([]() { return 42; });
int result = fut.get(); // блокирующее ожидание
Стандартная библиотека: контейнеры
Контейнеры — классы, хранящие наборы объектов. Все объявлены в заголовках <vector>, <list>, <map> и других.
Последовательные контейнеры
| Контейнер | Характеристики | Сложность доступа | Использование |
|---|---|---|---|
std::array<T, N> | Фиксированный размер, стек/статика | O(1) | Замена C-массивов |
std::vector<T> | Динамический массив | O(1) | Основной контейнер для большинства задач |
std::deque<T> | Двусторонняя очередь | O(1) на концах | Эффективные push_front/push_back |
std::list<T> | Двусвязный список | O(n) | Частые вставки/удаления в середине |
std::forward_list<T> | Односвязный список | O(n) | Минимальное потребление памяти |
Особенности:
vector— предпочтительный выбор по умолчаниюlistне поддерживает произвольный доступ (operator[])- Все контейнеры поддерживают итераторы
Ассоциативные контейнеры (упорядоченные)
| Контейнер | Ключ | Уникальность | Реализация | Сложность |
|---|---|---|---|---|
std::set<Key> | Key | уникальные | красно-чёрное дерево | O(log n) |
std::multiset<Key> | Key | дубликаты разрешены | то же | O(log n) |
std::map<Key, T> | Key → T | уникальные ключи | то же | O(log n) |
std::multimap<Key, T> | Key → T | дубликаты ключей | то же | O(log n) |
Упорядочены по ключу (по умолчанию — оператор <).
Неупорядоченные контейнеры (хэш-таблицы, C++11)
| Контейнер | Ключ | Уникальность | Средняя сложность |
|---|---|---|---|
std::unordered_set<Key> | Key | уникальные | O(1) |
std::unordered_multiset<Key> | Key | дубликаты | O(1) |
std::unordered_map<Key, T> | Key → T | уникальные ключи | O(1) |
std::unordered_multimap<Key, T> | Key → T | дубликаты ключей | O(1) |
Требования:
- Для ключа должен быть определён
std::hash<Key>или пользовательский хэш - Порядок элементов не гарантируется
Контейнеры-адаптеры
std::stack<T>— LIFO, основан наdequeпо умолчаниюstd::queue<T>— FIFO, основан наdequestd::priority_queue<T>— макс-куча, основан наvector
Пример:
std::priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
// pq.top() == 4
Итераторы
Интерфейс для обхода контейнеров. Классифицируются по возможностям:
| Тип | Возможности |
|---|---|
| Input Iterator | чтение, однократный проход вперёд |
| Output Iterator | запись, однократный проход вперёд |
| Forward Iterator | многократный проход вперёд, чтение/запись |
| Bidirectional Iterator | вперёд и назад (--it) |
| Random Access Iterator | произвольный доступ (it + n, it[n], сравнение) |
Соответствие контейнеров:
vector,deque,array→ random accesslist,set,map→ bidirectionalunordered_*→ forward
Алгоритмы принимают пары итераторов: [first, last)
Полезные функции:
begin(c),end(c)— итераторы начала и концаcbegin(c),cend(c)— константные итераторыrbegin(c),rend(c)— обратные итераторы
Алгоритмы (<algorithm>)
Работают с диапазонами через итераторы.
Немодифицирующие
std::find(first, last, value)— поиск значенияstd::count(first, last, value)— подсчёт вхожденийstd::all_of,any_of,none_of— проверка условийstd::for_each(first, last, func)— применение функции к каждому элементу
Модифицирующие
std::copy,std::move— копирование/перемещениеstd::fill,std::replace,std::transformstd::generate— заполнение с помощью генератора
Сортировка и упорядочение
std::sort(first, last)— быстрая сортировка (не стабильна)std::stable_sort— стабильная сортировкаstd::partial_sort— частичная сортировкаstd::nth_element— нахождение n-го элемента без полной сортировки
Поиск и сравнение
std::binary_search— в отсортированном диапазонеstd::equal,std::lexicographical_comparestd::mismatch— первое различие
Диапазоны (C++20)
Новый интерфейс без явных итераторов:
#include <ranges>
auto evens = vec | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * 2; });
Ленивые, компонуемые, безопасные.
Функциональные возможности
std::function
Обобщённая обёртка над callable-объектами:
#include <functional>
std::function<int(int, int)> op = [](int a, int b) { return a + b; };
int result = op(3, 4); // 7
Может хранить:
- Функции
- Лямбды
- Объекты с
operator() - Связанные функции (
std::bind)
std::bind (устаревает в пользу лямбд)
Фиксация аргументов функции:
void print(int a, int b, int c) { /* ... */ }
auto partial = std::bind(print, 1, std::placeholders::_1, 3);
partial(2); // вызовет print(1, 2, 3)
Современная замена — лямбда:
auto partial = [](int x) { print(1, x, 3); };
Работа со временем (<chrono>)
Типобезопасная работа с длительностями и моментами времени.
Длительности
std::chrono::seconds sec(5);
std::chrono::milliseconds ms = sec; // 5000 мс
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(ms);
Типы:
nanoseconds,microseconds,millisecondsseconds,minutes,hours
Часы
std::chrono::system_clock— системное время (можно преобразовать вtime_t)std::chrono::steady_clock— монотонные часы (подходят для замера интервалов)std::chrono::high_resolution_clock— самый точный таймер
Пример замера:
auto start = std::chrono::steady_clock::now();
// ... код ...
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
Временные точки
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::cout << std::ctime(&t);
Потоки ввода-вывода
Основные потоки (<iostream>)
std::cin— стандартный вводstd::cout— стандартный выводstd::cerr— ошибки (небуферизованный)std::clog— логирование (буферизованный)
Форматирование:
std::cout << std::hex << 255; // выведет "ff"
std::cout << std::setw(10) << std::setfill('0') << 42; // "0000000042"
Файловые потоки (<fstream>)
std::ifstream— чтение из файлаstd::ofstream— запись в файлstd::fstream— чтение и запись
Пример:
std::ofstream file("data.txt");
file << "Hello\n";
file.close();
Режимы открытия:
std::ios::in,out,app,binary,trunc
Строковые потоки (<sstream>)
std::stringstream— работа со строками как с потокамиstd::istringstream,std::ostringstream— только ввод/вывод
Пример парсинга:
std::string data = "123 45.6 true";
std::istringstream ss(data);
int i; double d; bool b;
ss >> i >> d >> std::boolalpha >> b;
Локализация и кодировки
Локали (<locale>)
Управление региональными настройками:
std::cout.imbue(std::locale("ru_RU.UTF-8"));
Влияет на:
- Формат чисел (разделитель дробной части)
- Сравнение строк (
collate) - Преобразование регистра (
ctype)
Кодировки
- Исходный код обычно в UTF-8
- Строковые литералы:
"..."— узкие строки (char), интерпретация зависит от системыu8"..."— UTF-8 (char)u"..."— UTF-16 (char16_t)U"..."— UTF-32 (char32_t)L"..."— широкие строки (wchar_t)
Для переносимой работы с Unicode рекомендуется:
- Хранить текст в
std::stringкак UTF-8 - Использовать сторонние библиотеки (например, ICU) для сложных операций
- Избегать
wchar_tвне Windows API
Модули (C++20)
Модули — современная замена заголовочным файлам, устраняющая проблемы с препроцессором, дублированием компиляции и утечкой приватных деталей.
Объявление модуля
Файл math.ixx (расширение зависит от компилятора):
export module math;
export int add(int a, int b) {
return a + b;
}
// Неэкспортированная функция — видна только внутри модуля
int helper() { return 42; }
Использование модуля
import math;
int main() {
int result = add(3, 4); // OK
// helper(); // ошибка: не экспортирована
}
Разделение на интерфейс и реализацию
// math.cppm
export module math;
export int add(int, int);
module : private;
// или просто:
int internal_impl(int a, int b) { return a + b; }
int add(int a, int b) {
return internal_impl(a, b);
}
Преимущества:
- Компиляция быстрее (нет повторного парсинга заголовков)
- Чёткая инкапсуляция
- Нет проблем с порядком
#include - Нет макросов, влияющих на внешний код
Поддержка:
- Clang ≥16, GCC ≥13, MSVC ≥19.28 (частично)
Корутины (C++20)
Корутины — функции, которые могут приостанавливать своё выполнение и возобновлять его позже.
Основные ключевые слова
co_await— приостановка до завершения асинхронной операцииco_yield— возврат значения и приостановка (генераторы)co_return— завершение корутины с результатом
Пример генератора
#include <coroutine>
#include <iostream>
generator<int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
for (int x : range(0, 5)) {
std::cout << x << " "; // 0 1 2 3 4
}
(Тип generator требует пользовательской реализации или библиотеки.)
Асинхронный вызов
task<int> fetch_data() {
auto data = co_await async_read_file("data.txt");
co_return parse(data);
}
Корутины требуют определения promise-типа, который управляет состоянием и возвратом.
Сложности:
- Высокий порог входа
- Требуется глубокое понимание жизненного цикла объектов
- Поддержка в стандартной библиотеке минимальна (ожидается в C++23/26)
Метапрограммирование и обобщённое программирование
constexpr
Функции и переменные, вычисляемые во время компиляции:
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int f = factorial(5); // вычислено на этапе компиляции
C++14+: тело constexpr-функции может содержать циклы, локальные переменные, ветвления.
consteval (C++20)
Гарантированно вычисляется только во время компиляции:
consteval int square(int x) { return x * x; }
// square(runtime_value); // ошибка компиляции
constinit (C++20)
Гарантирует инициализацию во время компиляции (для глобальных переменных):
constinit static int x = 42;
if constexpr (C++17)
Условная компиляция внутри шаблонов:
template <typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
// этот блок исчезает, если T — не целое
std::cout << "Integer: " << value << "\n";
} else {
std::cout << "Other: " << value << "\n";
}
}
Позволяет избежать SFINAE в простых случаях.
SFINAE (Substitution Failure Is Not An Error)
Механизм, при котором недопустимая подстановка шаблонных аргументов не вызывает ошибку, а просто исключает функцию из перегрузки.
Пример:
template <typename T>
auto has_begin(T&& t) -> decltype(t.begin(), std::true_type{});
template <typename T>
std::false_type has_begin(...);
Современная замена — концепции (C++20).
Концепции (Concepts, C++20)
Ограничения на шаблонные параметры:
template <typename T>
concept Addable = requires(T a, T b) {
a + b;
};
template <Addable T>
T sum(T a, T b) {
return a + b;
}
Стандартные концепции:
std::integral,std::floating_pointstd::copyable,std::movablestd::equality_comparable,std::totally_orderedstd::invocable,std::predicate
Best practices и распространённые ловушки
Правила проектирования
- RAII: ресурсы управляются временем жизни объектов
- Правило нуля: если можно избежать написания специальных функций-членов — не пишите их
- Избегайте сырых указателей в интерфейсах
- Предпочитайте
constпо умолчанию - Используйте
overrideиfinalдля виртуальных функций
Распространённые ошибки
- Двойной
deleteили использование послеdelete - Неправильное использование
deleteвместоdelete[] - Срезка объектов при передаче по значению базового класса
- Захват
thisв лямбде без проверки времени жизни - Гонки данных при многопоточности без синхронизации
- Неявные преобразования через однопараметрические конструкторы (используйте
explicit)
Производительность
- Избегайте ненужных копий — используйте ссылки и перемещение
- Резервируйте память в
vector, если известен размер - Используйте
emplace_backвместоpush_backдля сложных типов - Избегайте виртуальных вызовов в горячих циклах без профилирования
Инструменты разработки
Сборка
- CMake — кроссплатформенная система сборки:
add_executable(app main.cpp)
target_compile_features(app PRIVATE cxx_std_20) - Ninja, Make, MSBuild — генераторы
Компиляторы
- GCC:
-std=c++20 -Wall -Wextra -O2 -g - Clang: аналогично, с отличной диагностикой
- MSVC:
/std:c++20 /W4 /permissive-
Санитайзеры (sanitizers)
- AddressSanitizer (ASan) — утечки, выход за границы
- UndefinedBehaviorSanitizer (UBSan) — неопределённое поведение
- ThreadSanitizer (TSan) — гонки данных
Включение (GCC/Clang):
g++ -fsanitize=address -g -O1 app.cpp
Профилирование
- perf (Linux)
- VTune, Valgrind, gprof
- Встроенные профайлеры в Visual Studio, CLion
Статический анализ
- Cppcheck, PVS-Studio, Clang-Tidy
- Интеграция в CI для контроля качества
Форматирование
- clang-format — автоматическое форматирование по конфигурации
- Единый стиль в команде снижает когнитивную нагрузку